package mllib;

import common.ConstVars;
import org.apache.commons.math.linear.ArrayRealVector;
import org.apache.commons.math.linear.RealVector;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Scanner;

public class Classifier {
    public final static int PCAReducer = 1;
    public final static int LDAReducer = 2;
    public final static int NoReducer = 3;

    public final static int GEstimator = 4;
    public final static int KEstimator = 5;

    TrainingSet totalSet;
    Reducer reducer;
    HashMap<Estimator, String> estimators;
    String[] names;
    static double threshold = 0.0;
    private RealVector sampleMean;
    private int i = 2;
    private int cutoff;

    public static void main(String[] args)
    {
      Classifier c = null;
      if(args.length == 0)
      {
        System.err.println("Classifier args?");
        System.exit(0);
      }
      else if(args.length == 1)
      {
        c = new Classifier(args[0], 50);
      }
      else
      {
        c = new Classifier(args[0], args[1], 50);
      }
      for(int i = 0;i < c.names.length;i++)
      {
        try
        {
          System.out.println("Class for " + c.names[i] + ":" + c.identify(c.totalSet.getSample(i).getData()));
        }
        catch(Exception e) {
          e.printStackTrace();
        }
      }
    }
    
    public Classifier(String filename, String reducerFilename, int reducerType, int estimatorType, int cutoff)
    {
        this.cutoff = cutoff;
        estimators = new HashMap<Estimator, String>();

        parseFile(filename);

        System.out.println("STARTING REDUCTION");
        if(reducerType == Classifier.PCAReducer)
        {
            if(reducerFilename == null)
            {
                reducer = new PCA(totalSet, .93);
                reducer.toFile("pca.txt");
            }
            else
                reducer = new PCA(reducerFilename);
        }
        else if(reducerType == Classifier.LDAReducer)
        {
            if(reducerFilename == null)
            {
                reducer = new LDA(totalSet, names, .93);
                reducer.toFile("lda.txt");
            }
            else
                reducer = new LDA(reducerFilename);
        }
        else if(reducerType == Classifier.NoReducer)
        {
            if(reducerFilename == null)
            {
                reducer = new NoReducer(totalSet);
            }
            else
            {
                System.err.println("Reducer file for no reducer? Why?");
                System.exit(1);
            }
        }
        else
        {
            System.err.println("Not a valid reducerType!");
            System.exit(1);
        }
        System.out.println("FINISHED REDUCTION");

        //make unique names list
        HashSet<String> uniqueNames = new HashSet<String>();
        for(int i = 0;i < names.length;i++)
        {
            uniqueNames.add(names[i]);
        }

      for(String name: uniqueNames) {
        //make GE for each name
        if(name.equals(ConstVars.OTHER))
          continue;
        
        ArrayList<RealVector> m = new ArrayList<RealVector>();
        for(int i = 0;i < names.length;i++) {
          if(names[i].equals(name)) {
            try {
              m.add(reducer.projectPoint(totalSet.getSample(i)));
            }
            catch(Exception e)
            {
              e.printStackTrace();
            }
          }
        }
        int d = reducer.reducedDimension();
        double[][] matrix = new double[m.size()][d];
        for(int i = 0;i < m.size();i++) {
          matrix[i] = m.get(i).getData();
        }
        TrainingSet ts = new TrainingSet(matrix, true);
        System.out.println("Creating estimator for " + name);
        
        if( estimatorType == Classifier.KEstimator)
            estimators.put(new KMeansEstimator(ts,4), name); //4 = the number of k-classes for k-means
        else
            estimators.put(new GaussianEstimator(ts), name);
      }
    }
    public Classifier(String filename, String reducerFilename, int cutoff)
    { 
        this(filename, reducerFilename, Classifier.PCAReducer, Classifier.GEstimator, cutoff);
    }
    public Classifier(String filename, int reducerType, int cutoff)
    {
        this(filename, null, reducerType, Classifier.GEstimator,  cutoff);
    }
    public Classifier(String filename, int cutoff)
    {
         this(filename, null, Classifier.PCAReducer, Classifier.GEstimator, cutoff);
    }
    public Classifier(String filename, int redType, int estType, int cutoff)
    {
        this(filename, null, redType, estType, cutoff);
    }

    public void parseFile(String filename) {
        ArrayList<String> namesList = new ArrayList<String>();
        ArrayList<double[]> m = new ArrayList<double[]>();
        Scanner sc = null;

        try {
            sc = new Scanner(new File(filename));
            int dim = Integer.parseInt(sc.nextLine());
            while(sc.hasNextLine()) {
                namesList.add(sc.next());
                double[] vector = new double[dim];
                for(int i = 0; i < vector.length; i++) {
                    vector[i] = sc.nextDouble();
                }
                m.add(vector);
                sc.nextLine(); //DO I NEED THIS TO REMOVE NEWLINE?
            }

            double[][] matrix = new double[m.size()][dim];
            names = new String[namesList.size()];
            for(int i = 0; i < m.size(); i++) {
                matrix[i] = m.get(i);
                names[i] = namesList.get(i);
            }
            totalSet = new TrainingSet(matrix, true);
            sampleMean = totalSet.mean();
        } catch(Exception e) {
          e.printStackTrace();
        }
    }

    public String identify(double[] vector)
    {
      System.out.println(i++);
      if(vector == null)
        return "Other_other";
  
      String minName = ConstVars.OTHER;
        
      double minSDdist = cutoff;
      for(Estimator ge: estimators.keySet())
      {
        double dist = 0;
        try {
          dist = ge.prob(reducer.projectPoint(new ArrayRealVector(vector)));
        }
        catch(Exception e)
        {
          e.printStackTrace();
        }
        //if(dist * 1.5 < cutoff)
//            System.out.println("p for " + estimators.get(ge) + ": " + dist);
        if(dist < minSDdist) {
          minSDdist = dist;
          minName = estimators.get(ge);
        }

      }
      return minName;
    }
}
